package com.seeyon.ctp.rest.resources;

import com.alibaba.fastjson.JSONObject;
import com.kg.commons.utils.StringUtils;
import com.seeyon.apps.sphr.kit.FileKit;
import com.seeyon.apps.sphr.kit.FormKit;
import com.seeyon.apps.sphr.kit.HttpKit;
import com.seeyon.apps.sphr.kit.StrKit;
import com.seeyon.cap4.form.bean.FormBean;
import com.seeyon.cap4.form.bean.FormDataMasterBean;
import com.seeyon.cap4.form.service.CAP4FormManager;
import com.seeyon.cap4.template.manager.CAPDataListSqlHelpManager;
import com.seeyon.ctp.common.AppContext;
import com.seeyon.ctp.common.SystemEnvironment;
import com.seeyon.ctp.common.authenticate.domain.User;
import com.seeyon.ctp.common.ctpenumnew.manager.EnumManager;
import com.seeyon.ctp.common.log.CtpLogFactory;
import com.seeyon.ctp.common.po.ctpenumnew.CtpEnumBean;
import com.seeyon.ctp.common.po.ctpenumnew.CtpEnumItem;
import com.seeyon.ctp.organization.bo.V3xOrgDepartment;
import com.seeyon.ctp.organization.bo.V3xOrgMember;
import com.seeyon.ctp.organization.manager.OrgManager;
import com.seeyon.ctp.util.UUIDLong;
import org.apache.commons.logging.Log;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Path("jssdk")
public class ImportInvoiceBaiDuOrcResources extends BaseResource {

    private static final Log log = CtpLogFactory.getLog(ImportInvoiceBaiDuOrcResources.class);

    @Context
    HttpServletRequest req;

    private static String localAddr = AppContext.getRawRequest().getLocalAddr();
    private static String port = String.valueOf(AppContext.getRawRequest().getLocalPort());

    private static String tokenUrl = "http://" + localAddr + ":" + port + "/seeyon/rest/token";
    private static String fileUrl = "http://" + localAddr + ":" + port + "/seeyon/rest/attachment?token=";

    private static final String enumCode = AppContext.getSystemProperty("oa.enumCode");
    private static final String client_id = AppContext.getSystemProperty("baidu_orc.client_id");
    private static final String client_secret = AppContext.getSystemProperty("baidu_orc.client_secret");
    private static final String grant_type = AppContext.getSystemProperty("baidu_orc.grant_type");
    private static final String invoice_url = AppContext.getSystemProperty("baidu_orc.invoice_url");
    private static final String token_url = AppContext.getSystemProperty("baidu_orc.token_url");
    private static final String kflxm_unFlowformCode = AppContext.getSystemProperty("oaFormCode.kflxm_unFlowformCode");
    private static final String sslxm_unFlowformCode = AppContext.getSystemProperty("oaFormCode.sslxm_unFlowformCode");

    private EnumManager enumManager = (EnumManager) AppContext.getBean("enumManagerNew");
    private OrgManager orgManager = (OrgManager) AppContext.getBean("orgManager");
    private CAP4FormManager cap4FormManager = (CAP4FormManager) AppContext.getBean("cap4FormManager");
    private CAPDataListSqlHelpManager capDataListSqlHelpManager = (CAPDataListSqlHelpManager) AppContext.getBean("capDataListSqlHelpManager");

    /**
     * 获取发票类型数据
     * 该方法通过GET请求获取发票类型的相关数据，主要用于获取枚举类型的发票信息
     *
     * @param param 请求参数，包含枚举代码等信息
     * @return 返回一个Response对象，其中包含发票类型的数据或错误信息
     */
    @GET
    @Path("get/invoice/type/data")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces({MediaType.APPLICATION_JSON})
    public Response getInvoiceTypeData(Map<String, Object> param) {
        // 创建一个结果映射，用于存储返回的结果
        Map<String, Object> resultMap = new HashMap<>();
        try {
            // 根据枚举代码获取枚举对象
            CtpEnumBean ctpEnumBean = enumManager.getEnumByProCode(enumCode);
            // 如果枚举对象为空，则说明枚举代码维护有误，返回错误信息
            if (ctpEnumBean == null) {
                resultMap.put("code", 500);
                resultMap.put("message", "票据类型编码[" + enumCode + "]维护有误!");
                return ok(resultMap);
            }
            // 根据枚举ID获取枚举项列表
            List<CtpEnumItem> ctpEnumItemList = enumManager.getEmumItemByEmumId(ctpEnumBean.getId());
            // 如果枚举项列表为空，则返回错误信息
            if (ctpEnumItemList.isEmpty()) {
                resultMap.put("code", 500);
                resultMap.put("message", "根据关联枚举ID[" + ctpEnumBean.getId() + "]未查询到对应的枚举项!");
                return ok(resultMap);
            }
            // 创建一个数据列表，用于存储枚举项的数据
            List<Map<String, Object>> dataList = new ArrayList<Map<String, Object>>();
            // 遍历枚举项列表，将每个枚举项的数据添加到数据列表中
            for (CtpEnumItem ctpEnumItem : ctpEnumItemList) {
                Map<String, Object> dataMap = new HashMap<>();
                dataMap.put("id", StrKit.str(ctpEnumItem.getId()));
                dataMap.put("name", ctpEnumItem.getShowvalue());
                dataList.add(dataMap);
            }
            // 将数据列表添加到结果映射中，并设置状态码为200，表示成功
            resultMap.put("code", 200);
            resultMap.put("data", dataList);
            return ok(resultMap);
        } catch (Exception e) {
            // 如果发生异常，记录错误日志，并返回错误信息
            log.info("根据关联枚举编码查询枚举项报错信息:", e);
            resultMap.put("code", 500);
            resultMap.put("message", e.getMessage());
            return ok(resultMap);
        }
    }

    /**
     * 导入发票数据，并使用百度OCR进行数据解析
     * 该方法处理 multipart/form-data 类型的 POST 请求，用于上传发票及相关文件，并通过OCR技术解析发票信息
     * 同时，它也处理与发票相关的行程单附件上传和解析
     *
     * @return 返回一个包含处理结果的 Response 对象，包括状态码和消息
     */
    @POST
    @Path("importInvoiceBaiDuData")
    @Produces({MediaType.TEXT_HTML, MediaType.APPLICATION_JSON})
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response importInvoiceZhuanData() {
        // 初始化结果映射，用于存储处理状态和消息
        Map<String, Object> resultMap = new HashMap<>();
        try {
            // 获取发票类型，如果为空，则返回错误信息
            String invoiceType = StrKit.str(req.getHeader("invoiceType"));
            if ("".equals(invoiceType)) {
                resultMap.put("code", 500);
                resultMap.put("message", "票据类型[invoiceType]不能为空值!");
                return ok(resultMap);
            }
            String reimbursementType = StrKit.str(req.getHeader("reimbursementType"));
            if ("".equals(reimbursementType)) {
                resultMap.put("code", 500);
                resultMap.put("message", "报销类型[reimbursementType]不能为空值!");
                return ok(resultMap);
            }
            String projectCode = StrKit.str(req.getHeader("projectCode"));
            if ("".equals(projectCode)) {
                resultMap.put("code", 500);
                resultMap.put("message", "项目编码[projectCode]不能为空值!");
                return ok(resultMap);
            }
            String projectName = StrKit.str(req.getHeader("projectName"));
            if ("".equals(projectName)) {
                resultMap.put("code", 500);
                resultMap.put("message", "项目名称[projectName]不能为空值!");
                return ok(resultMap);
            }

            // 设置请求字符编码为 UTF-8
            req.setCharacterEncoding("UTF-8");
            // 使用 CommonsMultipartResolver 解析多部分请求
            CommonsMultipartResolver resolver = (CommonsMultipartResolver) AppContext.getBean("multipartResolver");
            MultipartHttpServletRequest multipartHttpServletRequest = resolver.resolveMultipart(req);
            MultiValueMap<String, MultipartFile> multiFileMap = multipartHttpServletRequest.getMultiFileMap();
            // 如果没有上传文件，则返回错误信息
            if (multiFileMap.isEmpty()) {
                resultMap.put("code", 500);
                resultMap.put("message", "请上传文件！！！");
                return ok(resultMap);
            }
            // 处理发票附件
            List<MultipartFile> invoice_fileList = multiFileMap.get("invoice");
            String systemUrl = SystemEnvironment.getBaseFolder().replaceAll("\\\\", "/");
            String pathUrl = systemUrl + "/upload/customFile/";
            File filePath = new File(pathUrl);
            if (!filePath.exists()) {
                filePath.mkdirs();
            }
            for (MultipartFile multipartFile : invoice_fileList) {
                String fileName = multipartFile.getOriginalFilename();
                String fileExtension = StringUtils.getFilenameExtension(fileName);
                String filePathUrl = pathUrl + "/" + fileName;
                File file = new File(filePathUrl);
                multipartFile.transferTo(file);
                // OCR识别解析发票
                String ocrResult = ocrRecognize(filePathUrl, fileExtension);
                User user = AppContext.getCurrentUser();
                String fileUrl = fileUpload(file, user);
                if (!"".equals(fileUrl)) {
                    Long sub_reference = UUIDLong.longUUID();
                    FormDataMasterBean formDataMasterBean = FormKit.addFormDataMasterBean(reimbursementType, projectName, projectCode, invoiceType, ocrResult, sub_reference, null, user);
                    FormKit.addAttachment(Long.parseLong(fileUrl), formDataMasterBean.getId(), sub_reference);
                }
                // 处理行程单附件
                if (multiFileMap.containsKey("journey")) {
                    List<MultipartFile> journey_fileList = multiFileMap.get("journey");
                    if (!journey_fileList.isEmpty()) {
                        for (MultipartFile journeyFile : journey_fileList) {
                            String journey_fileName = journeyFile.getOriginalFilename();
                            String journey_filePathUrl = pathUrl + "/" + journey_fileName;
                            File journey_file = new File(journey_filePathUrl);
                            journeyFile.transferTo(journey_file);
                            String journey_fileUrl = fileUpload(journey_file, user);
                            if (!"".equals(journey_fileUrl)) {
                                Long sub_reference = UUIDLong.longUUID();
                                FormDataMasterBean formDataMasterBean = FormKit.addFormDataMasterBean(reimbursementType, projectName, projectCode, invoiceType, ocrResult, null, sub_reference, user);
                                FormKit.addAttachment(Long.parseLong(journey_fileUrl), formDataMasterBean.getId(), sub_reference);
                            }
                        }
                    }
                }
            }
            // 返回成功信息
            resultMap.put("code", 200);
            resultMap.put("message", "");
            return ok(resultMap);
        } catch (Exception e) {
            // 异常处理，记录日志并返回错误信息
            log.info("解析OCR识别结果异常：", e);
            resultMap.put("code", 500);
            resultMap.put("message", e.getMessage());
            return ok(resultMap);
        }
    }

    /**
     * 获取项目数据信息
     *
     * @param param 包含查询参数的映射，如memberId
     * @return 返回一个Response对象，包含项目数据信息的JSON响应
     */
    @POST
    @Path("get/project/data")
    @Produces({MediaType.APPLICATION_JSON})
    @Consumes(MediaType.APPLICATION_JSON)
    public Response getProjectList(Map<String, Object> param) {
        // 初始化结果映射，用于存储响应数据
        Map<String, Object> resultMap = new HashMap<>();
        // 初始化数据列表，用于存储项目数据
        List<Map<String, Object>> dataList = new ArrayList<Map<String, Object>>();

        try {
            // 从参数中获取成员ID，并进行空值检查
            String memberId = StrKit.str(param.get("memberId"));
            if ("".equals(memberId)) {
                // 如果成员ID为空，返回错误响应
                resultMap.put("code", 500);
                resultMap.put("message", "人员ID不能为空值!");
                resultMap.put("data", dataList);
                return ok(resultMap);
            }

            // 根据成员ID获取成员信息，并进一步获取部门信息
            V3xOrgMember v3xOrgMember = orgManager.getMemberById(Long.parseLong(memberId));
            V3xOrgDepartment v3xOrgDepartment = orgManager.getDepartmentById(v3xOrgMember.getOrgDepartmentId());
            // 获取父部门代码，用于后续的条件判断
            String deptCode = orgManager.getParentDepartment(v3xOrgDepartment.getId()).getCode();

            // 初始化表单代码和员工姓名的显示名称
            String formCode = "";
            String ygxm_fieldDisplayName = "";

            // 根据部门代码确定表单代码和员工姓名的显示名称
            if (deptCode.equals("001")) {
                formCode = sslxm_unFlowformCode;
                ygxm_fieldDisplayName = "员工姓名-实施成员";
            } else if (deptCode.equals("002")) {
                formCode = kflxm_unFlowformCode;
                ygxm_fieldDisplayName = "员工姓名-开发人员";
            }

            // 根据表单代码获取表单对象
            FormBean formBean = cap4FormManager.getFormByFormCode(formCode);
            // 获取员工姓名、项目名称和项目编号的字段名称
            String ygxm_fieldName = formBean.getFieldBeanByDisplay(ygxm_fieldDisplayName).getName();
            String xmmc_fieldName = formBean.getFieldBeanByDisplay("项目名称").getName();
            String xmbm_fieldName = formBean.getFieldBeanByDisplay("项目编号").getName();
            // 获取主表和子表的表名
            String mainTableName = formBean.getMasterTableBean().getTableName();
            String subTableName = formBean.getFieldBeanByDisplay(ygxm_fieldDisplayName).getOwnerTableName();

            // 构建SQL查询语句
            StringBuffer stb = new StringBuffer();
            stb.append("SELECT f.id,").append(xmmc_fieldName).append(",").append(xmbm_fieldName);
            stb.append(" FROM ").append(mainTableName).append(" f LEFT JOIN ").append(subTableName);
            stb.append(" f1 ON f.ID = f1.formmain_id WHERE f1.").append(ygxm_fieldName).append(" = '").append(v3xOrgMember.getId()).append("'");
            if (param.containsKey("projectName")) {
                stb.append(" AND ").append(xmmc_fieldName).append(" LIKE '%").append(StrKit.str(param.get("projectName"))).append("'");
            }
            if (param.containsKey("projectCode")) {
                stb.append(" AND ").append(xmbm_fieldName).append(" LIKE '%").append(StrKit.str(param.get("projectCode"))).append("'");
            }
            // 执行SQL查询，获取项目数据列表
            List list = capDataListSqlHelpManager.selectDataList(stb.toString(), null);

            // 如果查询结果不为空，遍历并构建项目数据映射
            if (!list.isEmpty()) {
                for (int i = 0; i < list.size(); i++) {
                    Map<String, Object> map = new HashMap<String, Object>();
                    Map<String, Object> dataMap = (Map<String, Object>) list.get(i);
                    map.put("id", StrKit.str(dataMap.get("id")));
                    map.put("projectCode", StrKit.str(dataMap.get(xmbm_fieldName)));
                    map.put("projectName", StrKit.str(dataMap.get(xmmc_fieldName)));
                    dataList.add(map);
                }
            }

            // 构建成功响应并返回
            resultMap.put("code", 200);
            resultMap.put("message", "");
            resultMap.put("data", dataList);
            return ok(resultMap);
        } catch (Exception e) {
            // 异常处理，记录日志并返回错误响应
            log.info("获取项目数据信息异常:", e);
            resultMap.put("code", 500);
            resultMap.put("message", e.getMessage());
            resultMap.put("data", dataList);
            return ok(resultMap);
        }
    }

    /**
     * 使用OCR技术识别图片或PDF文件中的文字信息
     * 该方法首先将文件内容转换为Base64编码，然后请求百度OCR API获取access_token，
     * 并使用该token进行OCR识别请求，最后返回识别结果
     *
     * @param filePathUrl   文件路径或URL
     * @param fileExtension 文件扩展名，用于判断文件类型
     * @return OCR识别结果，格式为JSON字符串
     * @throws Exception 如果获取access_token失败或文件格式不支持，则抛出异常
     */
    private String ocrRecognize(String filePathUrl, String fileExtension) throws Exception {
        // 将文件内容转换为Base64编码
        String fileContentAsBase64 = FileKit.getFileContentAsBase64(filePathUrl, true);

        // 构造获取access_token的URL
        String tokenUrl = token_url + "?client_id=" + client_id + "&client_secret=" + client_secret + "&grant_type=" + grant_type;

        // 发送请求获取access_token
        String tokenResult = HttpKit.post(tokenUrl, new HashMap<>(), "");
        JSONObject jsonObject = JSONObject.parseObject(tokenResult);

        // 提取access_token
        String access_token = "";
        if (jsonObject.containsKey("access_token")) {
            access_token = StrKit.str(jsonObject.get("access_token"));
        } else {
            // 如果获取access_token失败，记录日志并抛出异常
            log.info("百度OCR识别获取access_token失败:" + tokenResult);
            throw new Exception("百度OCR识别获取access_token失败:" + tokenResult);
        }

        // 构造OCR识别请求的URL
        String ocrUrl = invoice_url + "?access_token=" + access_token;

        // 根据文件扩展名准备请求数据
        String data = "";
        if (fileExtension.equals("png") || fileExtension.equals("jpg") || fileExtension.equals("jpeg") || fileExtension.equals("bmp")) {
            data = "image=" + fileContentAsBase64;
        } else if (fileExtension.equals("pdf")) {
            data = "pdf_file=" + fileContentAsBase64;
        } else {
            // 如果文件格式不支持，则抛出异常
            throw new Exception("上传文件格式错误，仅支持【png/jpg/jpeg/bmp/pdf】格式的文件");
        }

        // 发送OCR识别请求并获取结果
        String ocrResult = HttpKit.postOcr(ocrUrl, new HashMap<>(), data);

        // 记录OCR识别返回信息
        log.info("百度OCR识别返回信息:" + ocrResult);

        // 返回OCR识别结果
        return ocrResult;
    }

    /**
     * 文件上传方法
     * 该方法负责将给定的文件上传到指定的URL，并根据上传结果返回文件的访问地址
     *
     * @param file 要上传的文件对象
     * @param user 执行上传操作的用户，用于获取登录名以生成认证令牌
     * @return 上传成功后返回文件的URL地址
     * @throws Exception 如果文件上传失败或解析上传结果出现问题，则抛出异常
     */
    private String fileUpload(File file, User user) throws Exception {
        // 根据用户登录名获取访问令牌，用于身份验证
        String tokenId = HttpKit.getToken(user.getLoginName(), tokenUrl);
        // 将令牌ID拼接到文件上传URL中，以传递认证信息
        String fileUrlNew = fileUrl + tokenId;
        // 执行文件上传并接收上传结果信息
        String fileResult = HttpKit.uploadFile(fileUrlNew, file);
        // 记录文件上传成功后的返回信息
        log.info("附件上传成功返回信息:" + fileResult);
        // 从上传结果中提取文件的访问URL
        String fileUrl = StrKit.getFileUrl(fileResult);
        // 如果提取的文件URL为空，则抛出异常，表示上传失败
        if ("".equals(fileUrl)) {
            throw new Exception("附件上传OA失败:" + fileResult);
        } else {
            // 上传成功后删除本地临时文件
            file.delete();
            // 记录文件上传成功后的文件地址
            log.info("附件上传OA成功，返回文件地址:" + fileUrl);
        }
        // 返回文件的访问地址
        return fileUrl;
    }
}
